import numpy as np
from copy import deepcopy
import random

class undersample():
    
    def __init__(self):
        '''
        待补充
        '''
    def nearmiss(self, more_data, low_data, epochs, rate = 1/4):
        '''
        more_data: 多数类数据
        low_data: 少数类数据
        epochs: 循环执行nearmiss次数
        rate: 少数类查寻点之间的最大距离系数
        '''
        tmp_more_data = more_data
        low_data_r = rate*np.max(self.p_distance(low_data, low_data))/2
        for epoch in range(epochs):
            re_index = []
            row, col = np.shape(low_data)
            for i in range(row):
                l = np.sum((tmp_more_data - low_data[i])**2, axis = 1).tolist()
                distance = min(l)
                index = l.index(distance)
              
                if index not in re_index and distance <= low_data_r:
                    re_index.append(index)
            tmp_more_data = np.delete(tmp_more_data, re_index, 0)
        return tmp_more_data
    
    def smote(self, supple_x, supplement = 4, k = 6, distype = 'p_distance', p=2):
        '''
        supple_x:待填充样本集合
        supplement: 每个样本的填充个数
         k: 每个样本的k近邻样本数
        distype: 判断相似度的方法
        p: p_distance方法中的参数
        '''
        new_supple_x = deepcopy(supple_x)
        k_m = self.knn(supple_x, k, distype, p)
        for i in range(len(k_m)):
            i_knn_samples = supple_x[k_m[i]]
            supple_list = []
            for j in range(supplement):
                new_sample = supple_x[i]+ random.uniform(0,1)*abs(supple_x[i]-random.choice(i_knn_samples))
                supple_list.append(new_sample)
            new_supple_x = np.concatenate((new_supple_x, np.array(supple_list)), axis = 0)
        return new_supple_x
            
    def knn(self, x, k=6, distype = 'p_distance', p=2):
        '''
        x: 数据集
        k: 每个样本的k近邻样本数
        distype: 判断相似度的方法
        p: p_distance方法中的参数
        '''
        x = np.array(x)
        if len(x.shape)==1:
            x = x.reshape(1,-1)
        k_m = []
        if distype == 'p_distance':
            distance_m = self.p_distance(x, x, p = p)
            for i in range(distance_m.shape[0]):
                l = -1*distance_m[i]
                k_m.append(self.get_k_samples(l, k))
        else:
            distance_m = self.cosim(x,x)
            for i in range(distance_m.shape[0]):
                l = distance_m[i]
                k_m.append(self.get_k_samples(l, k))
        return k_m
    
    def p_distance(self, x, y, p = 2):
        '''
        x 目标向量: 单一向量
        y 目标向量: 可单可多
        p 指数
        '''
        res = []
        x = np.array(x)
        if len(x.shape)==1:
            x = x.reshape(1, x.shape[0])
        y = np.array(y)
        if len(y.shape)==1:
            y = y.reshape(1, y.shape[0])
        if p>1:
            for i in range(x.shape[0]):
                dc_p = abs(y-x[i])**p
                res.append(np.sum(dc_p, axis = 1)**(1/p)) 
        else:
            for i in range(x.shape[0]):
                dc_p = abs(y-x[i])
                res.append(np.sum(dc_p, axis = 1))
        return np.array(res)
        
    def cosim(self, x, y):
        x = np.array(x)
        if len(x.shape)==1:
            x = x.reshape(1, x.shape[0])
        y = np.array(y)
        if len(y.shape)==1:
            y = y.reshape(1, y.shape[0])
        return np.dot(x,y.T)/np.dot(np.linalg.norm(x, axis=1, keepdims = True),\
                                    np.linalg.norm(y, axis=1, keepdims = True).T)
    
    def get_k_samples(self, l, k):
        d = {i:l[i] for i in range(len(l))}
        items = sorted(d.items() , key = lambda x: x[1], reverse = True)[1:k+1]
        return [items[i][0] for i in range(len(items))]